﻿//==============================================================================
// File: PropertyContainerType.cs
// Created: 2010-05-10
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with WpfRcp. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using WpfRcp.PropertyModel.Properties;

namespace WpfRcp.PropertyModel
{
    /// <summary>
    /// Represents the type of the concrete implementation of <see cref="PropertyContainer"/>.
    /// </summary>
    public sealed class PropertyContainerType
    {
        /// <summary>
        /// The Void interface serves only as a base for VOID type.
        /// </summary>
        private interface IVoid : IPropertyContainer {}

        /// <summary>
        ///  A cache for <see cref="PropertyContainerType"/> instances, indexed by type names.
        /// </summary>
        private static readonly IDictionary<string, PropertyContainerType> Cache = new Dictionary<string, PropertyContainerType>();

        /// <summary>
        /// The VOID type represents elements which are empty.
        /// </summary>
        public static readonly PropertyContainerType Void = FromType(typeof(IVoid));

        /// <summary>
        /// Initializes a new instance of the <see cref="PropertyContainerType"/> class.
        /// </summary>
        /// <param name="type">Type which is represented by this instance.</param>
        private PropertyContainerType(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            m_SystemType = type;
        }

        /// <summary>
        /// Associated CLR type.
        /// </summary>
        private readonly Type m_SystemType;

        /// <summary>
        /// Gets associated CLR type.
        /// </summary>
        public Type SystemType
        {
            get { return m_SystemType; }
        }

        /// <summary>
        /// Gets the name of this type.
        /// </summary>
        public string Name
        {
            get { return SystemType.Name; }
        }

        /// <summary>
        /// Checks whether given name represents valid type's name.
        /// </summary>
        /// <param name="typeName">Name to be validated.</param>
        /// <returns>True if name is valid. False otherwise.</returns>
        public static bool IsTypeNameValid(string typeName)
        {
            try
            {
                ParseTypeName(typeName);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        /// <summary>
        /// Gets <see cref="PropertyContainerType"/> for a given type name.
        /// </summary>
        /// <param name="typeName">Type name for which <see cref="PropertyContainerType"/> is requested.</param>
        /// <returns><see cref="PropertyContainerType"/> for requested the type name.</returns>
        public static PropertyContainerType FromString(string typeName)
        {
            Type type = ParseTypeName(typeName);
            return Create(type);
        }

        /// <summary>
        /// Gets <see cref="PropertyContainerType"/> for a given <see cref="System.Type"/>.
        /// </summary>
        /// <param name="type">Type for which <see cref="PropertyContainerType"/> is requested.</param>
        /// <returns><see cref="PropertyContainerType"/> for the requested type.</returns>
        public static PropertyContainerType FromType(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");

            return Create(type);
        }

        /// <summary>
        /// Checks if the current type is a subtype of a given type.
        /// </summary>
        /// <param name="type">Type to compare against.</param>
        /// <returns>True if the current type is a subtype of a given type. False otherwise.</returns>
        public bool IsSubtypeOf(PropertyContainerType type)
        {
            if (type.SystemType.IsAssignableFrom(SystemType))
                return true;

            return false;
        }

        /// <summary>
        /// Checks if the current type is a subtype of all types given in the collection.
        /// </summary>
        /// <param name="types">Types to compare against.</param>
        /// <returns>True if the current type is a subtype of all types given in the collection. False otherwise.</returns>
        public bool IsSubtypeOf(IEnumerable<PropertyContainerType> types)
        {
            foreach (var type in types)
            {
                if (IsSubtypeOf(type) == false)
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Gets all interfaces implemented by this type.
        /// </summary>
        public IEnumerable<PropertyContainerType> Interfaces
        {
            get
            {
                var interfaces = SystemType.GetInterfaces();
                foreach (var type in interfaces)
                {
                    if (type.IsAssignableFrom(SystemType) &&
                        typeof(IPropertyContainer).IsAssignableFrom(type) &&
                        type != typeof(IPropertyContainer))
                        yield return FromType(type);
                }
            }
        }

        private static PropertyContainerType Create(Type type)
        {
            Debug.Assert(type != null);

            if (!typeof(IPropertyContainer).IsAssignableFrom(type))
                throw new PropertyContainerException(
                    string.Format(Resources.ExceptionInvalidModelType, GetNameForType(type)));

            lock (Cache)
            {
                string typeName = GetNameForType(type);
                if (Cache.ContainsKey(typeName))
                    return Cache[typeName];

                var modelType = new PropertyContainerType(type);
                Cache.Add(typeName, modelType);

                return modelType;
            }
        }

        private static Type ParseTypeName(string typeName)
        {
            if (string.IsNullOrEmpty(typeName))
                throw new ArgumentNullException("typeName");

            Type type;

            try
            {
                type = Type.GetType(typeName);
            }
            catch (Exception e)
            {
                throw new PropertyContainerException(
                    string.Format(Resources.ExceptionTypeNotFound, typeName), e);
            }

            if (type == null)
                throw new PropertyContainerException(
                    string.Format(Resources.ExceptionTypeNotFound, typeName));

            if (!typeof(IPropertyContainer).IsAssignableFrom(type))
                throw new PropertyContainerException(
                    string.Format(Resources.ExceptionInvalidModelType, GetNameForType(type)));

            return type;
        }

        private static string GetNameForType(Type type)
        {
            return type.AssemblyQualifiedName;
        }

        #region Public Equality Implementation

        public bool Equals(PropertyContainerType other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.SystemType, SystemType);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof(PropertyContainerType)) return false;
            return Equals((PropertyContainerType)obj);
        }

        public override int GetHashCode()
        {
            return SystemType.GetHashCode();
        }

        public static bool operator ==(PropertyContainerType left, PropertyContainerType right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(PropertyContainerType left, PropertyContainerType right)
        {
            return !Equals(left, right);
        }

        #endregion

        #region Overrides

        public override string ToString()
        {
            return GetNameForType(SystemType);
        }

        #endregion
    }
}